/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/*
* Copyright (c) 2006 Georgia Tech Research Corporation, INRIA
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation;
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

#include "broadcom-egress-queue.h"

#include <stdio.h>
#include <iostream>
#include "ns3/log.h"
#include "ns3/enum.h"
#include "ns3/uinteger.h"
#include "ns3/double.h"
#include "ns3/simulator.h"
#include "ns3/drop-tail-queue.h"


NS_LOG_COMPONENT_DEFINE("BEgressQueue");

namespace ns3 {

NS_OBJECT_ENSURE_REGISTERED(BEgressQueue);

TypeId BEgressQueue::GetTypeId(void) {
	static TypeId tid = TypeId("ns3::BEgressQueue")
		.SetParent<Queue>()
		.AddConstructor<BEgressQueue>()
		.AddAttribute("MaxBytes",
			"The maximum number of bytes accepted by this BEgressQueue.",
			DoubleValue(1000.0 * 1024 * 1024),
			MakeDoubleAccessor(&BEgressQueue::m_maxBytes),
			MakeDoubleChecker<double>())
		.AddTraceSource ("BeqEnqueue", "Enqueue a packet in the BEgressQueue. Multiple queue",
				MakeTraceSourceAccessor (&BEgressQueue::m_traceBeqEnqueue),
				"ns3::Broadcom::TracedCallback")
		.AddTraceSource ("BeqDequeue", "Dequeue a packet in the BEgressQueue. Multiple queue",
				MakeTraceSourceAccessor (&BEgressQueue::m_traceBeqDequeue),
				"ns3::Broadcom::TracedCallback")
		;

    return tid;
}

BEgressQueue::BEgressQueue()
	: Queue<Packet>(), NS_LOG_TEMPLATE_DEFINE ("BEgressQueue") {
	NS_LOG_FUNCTION(this);
	m_bytesInQueueTotal = 0;
	m_rrlast = 0;
	for (uint32_t i = 0; i < qCnt; i++)
	{
		m_bytesInQueue[i] = 0;
		m_queues.push_back(CreateObject<DropTailQueue<Packet>>());
	}
}

BEgressQueue::~BEgressQueue() {
	NS_LOG_FUNCTION(this);
}

bool BEgressQueue::DoEnqueue(Ptr<Packet> p, uint32_t qIndex) {
	NS_LOG_FUNCTION(this << p);

	if (m_bytesInQueueTotal + p->GetSize() < m_maxBytes) { //infinite queue
		if (!m_queues[qIndex]->Enqueue(p)) {
			std::cout << "packet drop " << p->GetUid() << "\n";
		}
		m_bytesInQueueTotal += p->GetSize();
		m_bytesInQueue[qIndex] += p->GetSize();

		NS_LOG_LOGIC("m_traceEnqueue (p)");
		m_traceBeqEnqueue(p, qIndex);
		return true;
	} else {
		return false;
	}
}

// this is for switch only
Ptr<Packet> BEgressQueue::DoDequeueRR(bool paused[]) {
	NS_LOG_FUNCTION(this);

	if (m_bytesInQueueTotal == 0) {
		NS_LOG_LOGIC("Queue empty");
		return 0;
	}
	bool found = false;
	uint32_t qIndex;

	if (m_queues[0]->GetNPackets() > 0) { // 0 is the highest priority
		found = true;
		qIndex = 0;
	} else {
		if (!found) {
			for (qIndex = 1; qIndex <= qCnt; qIndex++) {
				if (!paused[(qIndex + m_rrlast) % qCnt] &&
					m_queues[(qIndex + m_rrlast) % qCnt]->GetNPackets() > 0) {
					// round robin
					found = true;
					break;
				}
			}
			qIndex = (qIndex + m_rrlast) % qCnt;
		}
	}
	if (found) {
		Ptr<Packet> p = m_queues[qIndex]->Dequeue();
		m_traceBeqDequeue(p, qIndex);
		m_bytesInQueueTotal -= p->GetSize();
		m_bytesInQueue[qIndex] -= p->GetSize();
		if (qIndex != 0) {
			m_rrlast = qIndex;
		}
		m_qlast = qIndex;
		NS_LOG_LOGIC("Popped " << p);
		NS_LOG_LOGIC("Number bytes " << m_bytesInQueueTotal);
		return p;
	}
	NS_LOG_LOGIC("Nothing can be sent");
	return 0;
}

bool BEgressQueue::Enqueue(Ptr<Packet> p, uint32_t qIndex) {
	NS_LOG_FUNCTION(this << p);
	// If DoEnqueue fails, Queue::Drop is called by the subclass
	return DoEnqueue(p, qIndex);
}

Ptr<Packet> BEgressQueue::DequeueRR(bool paused[]) {
	NS_LOG_FUNCTION(this);
	Ptr<Packet> packet = DoDequeueRR(paused);
	return packet;
}

// for compatiability
bool BEgressQueue::Enqueue(Ptr<Packet> p)	{
	std::cout << "Warning: Call Broadcom queues without priority\n";
	uint32_t qIndex = 0;
	NS_LOG_FUNCTION(this << p);
	if (m_bytesInQueueTotal + p->GetSize() < m_maxBytes) {
		m_queues[qIndex]->Enqueue(p);
		m_bytesInQueueTotal += p->GetSize();
		m_bytesInQueue[qIndex] += p->GetSize();
	} else {
		return false;
	}
	return true;
}


Ptr<Packet> BEgressQueue::Dequeue(void) {
	NS_ASSERT_MSG(false, "BEgressQueue::Dequeue not implemented");
	return 0;
}

Ptr<Packet> BEgressQueue::Remove(void) {
	NS_ASSERT_MSG(false, "BEgressQueue::Remove not implemented");
	return 0;
}

// DoPeek doesn't work for multiple queues!!
Ptr<const Packet> BEgressQueue::Peek(void) const {
	std::cout << "Warning: Call Broadcom queues without priority\n";
	NS_LOG_FUNCTION(this);
	if (m_bytesInQueueTotal == 0)
	{
		NS_LOG_LOGIC("Queue empty");
		return 0;
	}
	NS_LOG_LOGIC("Number bytes " << m_bytesInQueue);
	return m_queues[0]->Peek();
}

uint32_t BEgressQueue::GetNBytes(uint32_t qIndex) const {
	return m_bytesInQueue[qIndex];
}


uint32_t BEgressQueue::GetNBytesTotal() const {
	return m_bytesInQueueTotal;
}

uint32_t BEgressQueue::GetLastQueue() {
	return m_qlast;
}

}  // namespace ns3
